//#define FUSE_USE_VERSION 27
//#include <fuse_lowlevel.h>

#include "jolfs/buffer.h"
#include "jolfs/inode.h"

#include "jolfs/master_volume.h"
#include "jolfs/local_volume.h"
#include "jolfs/fuse_volume.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>

#include <sys/types.h>
#include <dirent.h>

#include <sstream>
#include <map>

// TODO:
//  - Open directory with the DirList structure.
//  - Close the directory and desallocate the DirList


//----------------------------------------------------------------------------
// GLOBAL
const char* local_mount = "/tmp";

jolfs::FuseVolume fuseVol;

struct DirEntry
{
  std::string name;
	
  inline DirEntry(const std::string& n) : name(n) {}
};


// Use to list directory.
struct DirList
{
  DIR* dir_;
  struct dirent last_entry_;	
};

typedef std::map<fuse_ino_t, DirEntry> DirEntryMap;
DirEntryMap entries;
fuse_ino_t  nextInodes;



//----------------------------------------------------------------------------

std::string join_path(const std::string& path, const std::string& name)
{
  return path + "/" + name;
}


//----------------------------------------------------------------------------

static void jolfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
{
	fuseVol.lookup(req, parent, name);

/*  
  fprintf(stderr, "[jolfs::fuse::lookup] parent ino: %ld name: %s\n", parent, name);
	
  // should return the information
  // about the entries.
  jolfs::EntryInfo ei;
  if(fuseVol.lookup(parent, name, ei)) {
    // send information about entry.
  } else {
    // send noent.
  }
  
  
  // dir entry map
  DirEntryMap::iterator demIt = entries.find(parent);
  if(demIt == entries.end()) {
    fuse_reply_err(req, ENOENT);
    return;
  }

  // compute path
  std::string path = join_path(demIt->second.name, name);

  // fetch information
  struct stat stbuf;
  lstat(path.c_str(), &stbuf);

  // insert the new entry.
  DirEntry de(path);
  entries.insert(std::make_pair(nextInodes, de));

  struct fuse_entry_param e;
  memset(&e, 0, sizeof(e));
  
  // set entry info
  e.ino = nextInodes;	
  e.attr_timeout  = 1.0;
  e.entry_timeout = 1.0;
  e.attr.st_ino   = nextInodes;
  e.attr.st_mode  = stbuf.st_mode;
	
  fprintf(stderr, "[jolfs::fuse::lookup] entry type: %d\n", e.attr.st_mode);

  // send reply
  fuse_reply_entry(req, &e);

  nextInodes++;

*/
}

//----------------------------------------------------------------------------
static void jolfs_getattr(fuse_req_t req, fuse_ino_t ino,
			  struct fuse_file_info *fi)
{
  (void) fi;
  struct stat stbuf;
  memset(&stbuf, 0, sizeof(stbuf));

  fprintf(stderr, "[jolfs::fuse::getattr] ino: %ld\n", ino);

  if(ino == 1) {

    stbuf.st_ino   = ino;
    stbuf.st_mode  = S_IFDIR | 0755;
    stbuf.st_nlink = 2;
	
    fuse_reply_attr(req, &stbuf, 1.0);
  } else {
    fuse_reply_err(req, ENOENT);
  }
}

//----------------------------------------------------------------------------
// TODO: Need to send an empty buffer to notify that we are
//       at the end of the readdir.
static void jolfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
			  off_t off, struct fuse_file_info *fi)
{
  fprintf(stderr, "[jolfs::fuse::readdir] ino: %ld size: %d offset: %lld\n", ino, size, off);

  if(off > 0) {
    fuse_reply_buf(req, NULL, 0);
    return;
  }

  (void) fi;
  fprintf(stderr, "[jolfs::fuse::readdir] ino: %ld size: %d offset: %lld\n", ino, size, off);
  if(ino == 1) {
    // list the mount directory
    DIR* root_dir = opendir(local_mount);

    if(!root_dir) {
      fprintf(stderr, "[jolfs::fuse::readdir] fail to read dir ino: %ld\n", ino);
      fuse_reply_err(req, ENOENT);
    }
		
    // browse each entry of the current directory.
    struct dirent current_entry;
    struct dirent* dir_result  = NULL;

    // buffer
    jolfs::Buffer buf;

    struct stat stbuf;
    memset(&stbuf, 0, sizeof(stbuf));

    while( readdir_r(root_dir, &current_entry, &dir_result) == 0 && dir_result != NULL) {
      // for each entry create a fuse entry
      stbuf.st_ino  = dir_result->d_ino;
      stbuf.st_mode = 0;
      if     (dir_result->d_type & DT_DIR) stbuf.st_mode = S_IFDIR;
      else if(dir_result->d_type & DT_LNK) stbuf.st_mode = S_IFLNK;
      else if(dir_result->d_type & DT_REG) stbuf.st_mode = S_IFREG;
      else if(dir_result->d_type == DT_UNKNOWN) {
	// if we here it is maybe the underlying filesystem
	// does not implement very well the d_type.
	std::stringstream ss;
	ss << root_dir  << "/" << dir_result->d_name;
	struct stat tmpStat;
	memset(&tmpStat, 0, sizeof(tmpStat));
	lstat(ss.str().c_str(), &tmpStat);
	stbuf.st_ino  = dir_result->d_ino;
	stbuf.st_mode = tmpStat.st_mode;
      } else {
	fprintf(stderr, "[jolfs::readdir] not supported mode for entry: %s [%d]\n", 
		dir_result->d_name, dir_result->d_type);
	continue;
      }
			
      // detect the increment size to use
      size_t tmpSize = fuse_add_direntry(req, NULL, 0, dir_result->d_name, NULL, 0);
      buf.ensure(tmpSize); 
      fprintf(stderr, "[jolfs::readdir] increment size entry: %d ino: %lld\n", tmpSize, stbuf.st_ino);
      fuse_add_direntry(req, (char*)buf.wr(), tmpSize, 
			dir_result->d_name, &stbuf, buf.length() + tmpSize);			
      buf.wr(tmpSize);
    }

    closedir((DIR*) fi->fh);
		
    if(buf.length()) {
      fprintf(stderr, "[jolfs::readdir] fuse_reply_buf size: %d\n", buf.length());
      fuse_reply_buf(req, (const char*)buf.rd(), buf.length());
    }
    
  } else {
    // all other directory
    fprintf(stderr, "[jolfs::fuse::readdir] other than root directory\n");
    fuse_reply_err(req, ENOENT);
  }
}

//----------------------------------------------------------------------------

struct fuse_lowlevel_ops makeJolFs()
{
  struct fuse_lowlevel_ops fs;
  memset(&fs, 0, sizeof(fs));
  // init fs
  fs.lookup  = jolfs_lookup;
  
  fs.getattr = jolfs_getattr; 
  fs.readdir = jolfs_readdir;
  
  return fs;
};

//----------------------------------------------------------------------------
// MAIN ENTRY POINT
//----------------------------------------------------------------------------


int main(int argc, char *argv[])
{
  // define the local entry point.
  entries.insert(std::make_pair(1, DirEntry(local_mount)));
  nextInodes = 2;
  
  // INIT ...
  struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
  struct fuse_chan *ch;
  int err = -1;
  char *mountpoint;

  fuseVol.master().appendVolume(new jolfs::LocalVolume("Local tmp", local_mount));

  
  // PARSE ARGS
  
  if (fuse_parse_cmdline(&args, &mountpoint, NULL, NULL) != -1 &&
      (ch = fuse_mount(mountpoint, &args)) != NULL) {
    struct fuse_session *se;
    
    // build fs
    struct fuse_lowlevel_ops fs = makeJolFs();
    
    // create fuse session
    se = fuse_lowlevel_new(&args, &fs,
			   sizeof(fs), NULL);
    
    // on fuse session, set sighandler + fuse_session_loop.
    if (se != NULL) {
      if (fuse_set_signal_handlers(se) != -1) {
	fuse_session_add_chan(se, ch);
	
	// infinite loop.
	err = fuse_session_loop(se);
	fuse_remove_signal_handlers(se);
	fuse_session_remove_chan(ch);
      }
      fuse_session_destroy(se);
    }
    
    // umount 
    fuse_unmount(mountpoint, ch);
  }
  fuse_opt_free_args(&args);
  
  fuseVol.master().shutdown();

  return err ? 1 : 0;
}
